/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the ohosannotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.holder;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JClassAlreadyExistsException;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JExpr;
import com.helger.jcodemodel.JFieldVar;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JMod;
import com.helger.jcodemodel.JVar;

import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.api.sharedpreferences.BooleanPrefEditorField;
import org.ohosannotations.api.sharedpreferences.EditorHelper;
import org.ohosannotations.api.sharedpreferences.FloatPrefEditorField;
import org.ohosannotations.api.sharedpreferences.IntPrefEditorField;
import org.ohosannotations.api.sharedpreferences.LongPrefEditorField;
import org.ohosannotations.api.sharedpreferences.SharedPreferencesHelper;
import org.ohosannotations.api.sharedpreferences.StringPrefEditorField;
import org.ohosannotations.api.sharedpreferences.StringPrefField;
import org.ohosannotations.api.sharedpreferences.StringSetPrefEditorField;
import org.ohosannotations.helper.CanonicalNameConstants;

import java.util.HashMap;
import java.util.Map;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;

import static com.helger.jcodemodel.JMod.FINAL;
import static com.helger.jcodemodel.JMod.PUBLIC;
import static com.helger.jcodemodel.JMod.STATIC;
import static org.ohosannotations.helper.ModelConstants.classSuffix;
import static org.ohosannotations.helper.ModelConstants.generationSuffix;

/**
 * 共享”持有人
 *
 * @author dev
 * @since 2021-07-21
 */
public class SharedPrefHolder extends BaseGeneratedClassHolder {
    /**
     * 编辑字段持有人
     *
     * @author dev
     * @since 2021-07-22
     */
    private static class EditorFieldHolder {
        /**
         * 领域类
         */
        public final Class<?> fieldClass;
        /**
         * 场方法名称
         */
        public final String fieldMethodName;

        /**
         * 编辑字段持有人
         *
         * @param fieldClass 领域类
         * @param fieldMethodName 场方法名称
         */
        EditorFieldHolder(Class<?> fieldClass, String fieldMethodName) {
            this.fieldClass = fieldClass;
            this.fieldMethodName = fieldMethodName;
        }
    }


    private static final Map<String, EditorFieldHolder> EDITOR_FIELD_BY_TYPE
        = new HashMap<String, EditorFieldHolder>() {
        private static final long serialVersionUID = 1L;

        {
            put("boolean", new EditorFieldHolder(BooleanPrefEditorField.class, "booleanField"));
            put("float", new EditorFieldHolder(FloatPrefEditorField.class, "floatField"));
            put("int", new EditorFieldHolder(IntPrefEditorField.class, "intField"));
            put("long", new EditorFieldHolder(LongPrefEditorField.class, "longField"));
            put(CanonicalNameConstants.STRING, new EditorFieldHolder(StringPrefEditorField.class, "stringField"));
            put(CanonicalNameConstants.STRING_SET,
                new EditorFieldHolder(StringSetPrefEditorField.class, "stringSetField"));
        }
    };

    private JMethod constructor;
    private JBlock constructorSuperBlock;
    private JVar constructorContextParam;
    private JFieldVar contextField;
    private JDefinedClass editorClass;
    private JFieldVar editorContextField;
    private JMethod editorConstructor;
    private JInvocation editMethodEditorInvocation;

    /**
     * 共享”持有人
     *
     * @param environment 环境
     * @param annotatedElement 带注释的元素
     * @throws Exception 异常
     */
    public SharedPrefHolder(OhosAnnotationsEnvironment environment, TypeElement annotatedElement) throws Exception {
        super(environment, annotatedElement);
        createEditorClass();
        createEditMethod();
    }

    /**
     * 设置扩展
     */
    @Override
    protected void setExtends() {
        generatedClass._extends(SharedPreferencesHelper.class);
    }

    /**
     * 创建编辑器类
     *
     * @throws JClassAlreadyExistsException jclass已经存在异常
     */
    private void createEditorClass() throws JClassAlreadyExistsException {
        String interfaceSimpleName = annotatedElement.getSimpleName().toString();
        editorClass = generatedClass._class(PUBLIC | STATIC | FINAL, interfaceSimpleName + "Editor" + classSuffix());
        editorClass._extends(getJClass(EditorHelper.class).narrow(editorClass));

        createEditorConstructor();
    }

    /**
     * 创建编辑器的构造函数
     */
    private void createEditorConstructor() {
        editorConstructor = editorClass.constructor(JMod.NONE);
        JVar sharedPreferencesParam = editorConstructor.param(getClasses().PREFERENCE, "preferences");
        editorConstructor.body().invoke("super").arg(sharedPreferencesParam);
    }

    /**
     * 创建编辑方法
     */
    private void createEditMethod() {
        JMethod editMethod = generatedClass.method(PUBLIC, editorClass, "edit");
        editMethodEditorInvocation = JExpr._new(editorClass)
            .arg(JExpr.invoke("getSharedPreferences"));
        editMethod.body()._return(editMethodEditorInvocation);
    }

    /**
     * 创建领域的方法
     *
     * @param prefFieldHelperClass ”字段的助手类
     * @param keyExpression 关键表达式
     * @param fieldName 字段名
     * @param fieldHelperMethodName 场辅助方法名称
     * @param defaultValue 默认值
     * @param docComment 医生的评论
     * @param defaultValueStr str默认值
     */
    public void createFieldMethod(Class<?> prefFieldHelperClass, IJExpression keyExpression,
        String fieldName, String fieldHelperMethodName, IJExpression defaultValue,
        String docComment, String defaultValueStr) {
        JMethod fieldMethod = generatedClass.method(PUBLIC, prefFieldHelperClass, fieldName);
        if (defaultValueStr != null) {
            boolean isStringPrefField = StringPrefField.class == prefFieldHelperClass;
            final String defaultValueJavaDoc;
            if (isStringPrefField) {
                defaultValueJavaDoc = "\"" + defaultValueStr + "\"";
            } else {
                defaultValueJavaDoc = defaultValueStr;
            }
            fieldMethod.javadoc().append("<p><b>Defaults to</b>: "
                + defaultValueJavaDoc + "</p>" + System.lineSeparator());
        }
        codeModelHelper.addTrimmedDocComment(fieldMethod, docComment);
        fieldMethod.javadoc().addReturn().append("a {@link "
            + prefFieldHelperClass.getSimpleName() + "} instance to retrieve or write the pref value");
        fieldMethod.body()._return(JExpr
            .invoke(fieldHelperMethodName).arg(keyExpression).arg(defaultValue));
    }

    /**
     * 创建编辑字段的方法
     *
     * @param method 方法
     * @param keyExpression 关键表达式
     */
    public void createEditorFieldMethods(ExecutableElement method, IJExpression keyExpression) {
        String returnType = method.getReturnType().toString();
        EditorFieldHolder editorFieldHolder = EDITOR_FIELD_BY_TYPE.get(returnType);
        AbstractJClass editorFieldClass = getJClass(editorFieldHolder.fieldClass);
        String fieldName = method.getSimpleName().toString();
        JMethod editorFieldMethod = editorClass.method(PUBLIC, editorFieldClass.narrow(editorClass), fieldName);
        String docComment = getProcessingEnvironment().getElementUtils().getDocComment(method);
        codeModelHelper.addTrimmedDocComment(editorFieldMethod, docComment);
        editorFieldMethod.body()._return(JExpr.invoke(editorFieldHolder.fieldMethodName).arg(keyExpression));
    }

    /**
     * 构造函数得到超级块
     *
     * @return {@link JBlock}
     */
    public JBlock getConstructorSuperBlock() {
        if (constructorSuperBlock == null) {
            setConstructor();
        }
        return constructorSuperBlock;
    }

    /**
     * 获取构造函数上下文参数
     *
     * @return {@link JVar}
     */
    public JVar getConstructorContextParam() {
        if (constructorContextParam == null) {
            setConstructor();
        }
        return constructorContextParam;
    }

    /**
     * 获取构造函数
     *
     * @return {@link JMethod}
     */
    public JMethod getConstructor() {
        if (constructor == null) {
            setConstructor();
        }
        return constructor;
    }

    /**
     * 集的构造函数
     */
    private void setConstructor() {
        constructor = generatedClass.constructor(PUBLIC);
        constructorContextParam = constructor.param(getClasses().CONTEXT, "context");
        JBlock constructorBody = constructor.body();
        constructorSuperBlock = constructorBody.blockSimple();
    }

    /**
     * 获得上下文字段
     *
     * @return {@link JFieldVar}
     */
    public JFieldVar getContextField() {
        if (contextField == null) {
            setContextField();
        }
        return contextField;
    }

    /**
     * 设置上下文字段
     */
    protected void setContextField() {
        contextField = generatedClass.field(JMod.PRIVATE, getClasses().CONTEXT, "context" + generationSuffix());
        getConstructor().body().assign(JExpr._this().ref(contextField), getConstructorContextParam());
    }

    /**
     * 得到编辑上下文字段
     *
     * @return {@link JFieldVar}
     */
    public JFieldVar getEditorContextField() {
        if (editorContextField == null) {
            setEditorContextField();
        }
        return editorContextField;
    }

    /**
     * 集编辑器上下文字段
     */
    protected void setEditorContextField() {
        editorContextField = editorClass.field(JMod.PRIVATE, getClasses().CONTEXT, "context" + generationSuffix());
        JVar contextParam = editorConstructor.param(getClasses().CONTEXT, "context");
        editorConstructor.body().assign(JExpr._this().ref(editorContextField), contextParam);
        editMethodEditorInvocation.arg(getContextField());
    }
}
